#include "ship.h"
#include "ship_template.h"
#include "slot_template.h"
#include "ship_types.h"
#include "slot_types.h"
#include "json/value.h"

using namespace std;

Ship::Ship( string _name, int _thrust, const ShipTemplate & _ship ) : CostChanger(_ship.getCost()), name(_name), ownShipTemplate(_ship) {
	// These are included in the cost just below
	// These are just the default values in the constructor!
	int baseFillType = ARMOR_FILL_TYPE;
	int baseTowerType = NO_TOWER;

	// Setting up hull
	hull.actualHp = ownShipTemplate.getMaxHp();

	// Setting up hull.dmg
	hull.dmg.noTurnLeft = false;
	hull.dmg.noTurnRight = false;

	hull.dmg.speedLoss = 0;
	hull.dmg.heightLoss = 0;
	// hull.dmg.noControlTower follows

	// hull.thrust follows

	// Setting up hull.dmg.noControlTower + hull.controlTowers
	for ( int i = 0; i < ownShipTemplate.getTowers(); i++ ) {
		hull.controlTowers.push_back(baseTowerType);
		hull.dmg.noControlTower.push_back(false);
		addCost( TOWER_COST(baseTowerType) );
	}
	
	// Setting up slots
	for ( int i = 0; i < 6; i++ ) {
		vector<SlotTemplate> temp = ownShipTemplate.getSide(i);
		for ( auto &a : temp ) {
			SlotStatus temp = {a.getMaxHp(), baseFillType};
			sides[i].push_back(temp);
			addCost( FILL_COST(baseFillType) );
		}
	}

	setThrust(_thrust);

	x = y = direction = 0;
	h = 3;
}

Ship::Ship( const Json::Value & _ship, const ShipTemplate & _ts, string _name ) : CostChanger(_ts.getCost()), name(_name), ownShipTemplate(_ts) {
	
	setThrust(_ship["ShipStatus"]["Thrust"].asInt());
	
	for ( unsigned int i = 0; i < (unsigned int)ownShipTemplate.getTowers(); i++ ) {
		int towerType = _ship["ShipStatus"]["ControlTower"][i].asInt();
		hull.controlTowers.push_back(towerType);
		hull.dmg.noControlTower.push_back(_ship["ShipStatus"]["StructuralDamages"]["NoControlTower"][i].asBool());
		// Update cost && check that type is correct
		addCost( TOWER_COST(towerType) );
	}

	setHp(_ship["ShipStatus"]["ActualHp"].asInt());

	hull.dmg.noTurnLeft = _ship["ShipStatus"]["StructuralDamages"]["NoTurnLeft"].asBool();
	hull.dmg.noTurnRight = _ship["ShipStatus"]["StructuralDamages"]["NoTurnRight"].asBool();
	hull.dmg.speedLoss = _ship["ShipStatus"]["StructuralDamages"]["SpeedLoss"].asInt();
	hull.dmg.heightLoss = _ship["ShipStatus"]["StructuralDamages"]["HeightLoss"].asInt();

	
	for ( int i = 0; i < 6; i++ ) {
		vector<SlotTemplate> temp = ownShipTemplate.getSide(i);
		for ( unsigned int j = 0; j < temp.size(); j++ ) {
			int fillType = _ship["SlotStatus"][i][j]["Loadout"].asInt();
			SlotStatus temp = {_ship["SlotStatus"][i][j]["ActualHp"].asInt(),
							   fillType};
			sides[i].push_back(temp);
			// Update cost && check that type is correct
			addCost( FILL_COST(fillType) );
		}
	}

	x = _ship["Position"]["x"].asInt();
	y = _ship["Position"]["y"].asInt();
	h = _ship["Position"]["h"].asInt();
	direction = _ship["Position"]["direction"].asInt();

	if ( _name == "" )
		name = _ship["Name"].asString();
}

Json::Value Ship::convertToJson(bool _templateToo) const {
	Json::Value ship;

	if ( _templateToo )
		ship["Template"] = ownShipTemplate.convertToJson();

	ship["Name"] = name;
	ship["Type"] = "Ship";
	ship["Cost"] = getCost();
	ship["ShipStatus"]["ActualHp"] = hull.actualHp;
	ship["ShipStatus"]["Thrust"] = hull.thrust;
	ship["ShipStatus"]["StructuralDamages"]["NoTurnLeft"] = hull.dmg.noTurnLeft;
	ship["ShipStatus"]["StructuralDamages"]["NoTurnRight"] = hull.dmg.noTurnRight;
	ship["ShipStatus"]["StructuralDamages"]["SpeedLoss"] = hull.dmg.speedLoss;
	ship["ShipStatus"]["StructuralDamages"]["HeightLoss"] = hull.dmg.heightLoss;

	for( unsigned int i = 0; i < hull.controlTowers.size(); i++ ) {
		ship["ShipStatus"]["StructuralDamages"]["NoControlTower"][i] = hull.dmg.noControlTower[i];
		ship["ShipStatus"]["ControlTower"][i] = hull.controlTowers[i];
	}

	for( unsigned int i = 0; i < 6; i++ ) {
		for( unsigned int j = 0; j < sides[i].size(); j++ ) {
			ship["SlotStatus"][i][j]["ActualHp"] = sides[i][j].actualHp; 
			ship["SlotStatus"][i][j]["Loadout"] = sides[i][j].loadout; 
		}
	}

	ship["Position"]["x"] = x;
	ship["Position"]["y"] = y;
	ship["Position"]["h"] = h;
	ship["Position"]["direction"] = direction;

	return ship;
}

void Ship::resetStructuralDamages() {
	hull.dmg.noTurnLeft = false;
	hull.dmg.noTurnRight = false;
	hull.dmg.speedLoss = 0;
	hull.dmg.heightLoss = 0;
	for ( int i = 0; i < ownShipTemplate.getTowers(); i++ )
		hull.dmg.noControlTower[i] = false;
}

void Ship::setThrust(int _thrust) {
	if ( _thrust < MIN_THRUST ||
		 _thrust > MAX_THRUST ) throw invalid_argument("Ship::setThrust(int)");
	hull.thrust = _thrust;
	// FIXME: COSTS
}

void Ship::setControlTowerLoadout(int _tower, int _loadout) {
	int newCost = TOWER_COST(_loadout); // Checks new value
	int oldLoad = hull.controlTowers.at(_tower);

	hull.controlTowers.at(_tower) = _loadout;

	addCost( newCost - TOWER_COST(oldLoad) );
}

void Ship::setSlotLoadout(int _side, int _slot, int _loadout) {
	int newCost = FILL_COST(_loadout); // Checks new value
	int oldLoad = sides.at(_side).at(_slot).loadout;

	// Check if it is actually possible to equip this loadout
	if ((_loadout == ARMOR_FILL_TYPE)||getShipTemplate()->getSlotTemplate(_side,_slot).getAllowedCannons())
		sides.at(_side).at(_slot).loadout = _loadout;
	else
		throw runtime_error("Ship::setSlotLoadout(int,int,int)");

	addCost( newCost - FILL_COST(oldLoad) );
}

void Ship::setPosition( int _x, int _y ) {
	x = _x;
	y = _y;
}

void Ship::setDirection( int _dir ) {
	if ( _dir < MIN_DIRECTION ||
		 _dir > MAX_DIRECTION ) throw invalid_argument("Ship::setDirection(int)");

	direction = _dir;
}

void Ship::move( int _dir ) {
	if ( _dir < MIN_DIRECTION ||
		 _dir > MAX_DIRECTION )
		throw invalid_argument("Ship::move(int)");

	direction = _dir;
	
	switch ( _dir ) {
		case DIRECTION_NORTH_EAST: {
			x++; y++;
			break;
		}
		case DIRECTION_NORTH_WEST: {
			y++;
			break;
		}
		case DIRECTION_EAST: {
			x++;
			break;
		}
		case DIRECTION_WEST: {
			x--;
			break;
		}
		case DIRECTION_SOUTH_EAST: {
			y--;
			break;
		}
		case DIRECTION_SOUTH_WEST: {
			x--; y--;
			break;
		}
		default:
			throw runtime_error("Ship::move(int)");
	}

}

bool Ship::reduceHp() {
	if ( hull.actualHp > 0 )
		hull.actualHp--;

	return hull.actualHp;
}

void Ship::resetHp() {
	hull.actualHp = ownShipTemplate.getMaxHp();
}

void Ship::setHp( int _hp ) {
	if ( _hp < 0 || _hp > ownShipTemplate.getMaxHp() ) throw invalid_argument("Ship::setHp(int)");

	hull.actualHp = _hp;
}

void Ship::resetSidesHp() {
	for ( int i = 0; i < 6; i++ )
		resetSideHp(i);
}

void Ship::resetSideHp(int _side) {
		vector<SlotTemplate> tSides = ownShipTemplate.getSide(_side);
		int size = sides.size();
		for ( int j = 0; j < size; j++ )
			sides[_side][j].actualHp = tSides.at(j).getMaxHp();
}

void Ship::resetSlotHp(int _side, int _slot) {
	sides[_side][_slot].actualHp = ownShipTemplate.getSide(_side).at(_slot).getMaxHp();
}

bool Ship::damageTurnLeft() {
	return hull.dmg.noTurnLeft++;	
}

bool Ship::damageTurnRight() {
	return hull.dmg.noTurnRight++;
}

bool Ship::damageMaxSpeed() {
	int maxSpeed = MAX_SPEED(hull.thrust);
	return ( ++hull.dmg.speedLoss >= maxSpeed ? ( hull.dmg.speedLoss = maxSpeed ) : false );
}

bool Ship::damageMaxHeight() {
	return ( ++hull.dmg.heightLoss >= MAX_HEIGHT ? ( hull.dmg.heightLoss = MAX_HEIGHT ):false );
}

bool Ship::damageControlTower(int _tower) {
	bool temp = hull.dmg.noControlTower[_tower];
	hull.dmg.noControlTower[_tower] = true;
	return temp;
}

array<string,3> Ship::getSummary() const {
	array<string,3> summary;

	summary[0] = name;
	summary[1] = ownShipTemplate.name;
	summary[2] = std::to_string(getCost());
	
	return summary;
}

vector<array<string,5>> Ship::getSlotLoadoutSummaries() const {
	vector<array<string,5>> summary;

	array<string,5> temp;
	for ( int i = MIN_SIDE; i < ALL_SIDES; i++ ) {
		int j = 0;
		for ( auto & it : sides.at(i) ) {
			temp[0] = STRING_SIDE(i);
			temp[1] = STRING_DIRS(ownShipTemplate.getSlotTemplate(i,j).getDirections()); 
			temp[2] = STRING_AC(ownShipTemplate.getSlotTemplate(i,j).getAllowedCannons());
			temp[3] = STRING_FILL(it.loadout);
			temp[4] = std::to_string(FILL_COST(it.loadout));

			summary.push_back(temp);
			j++;
		}
	}
	return summary;
}

vector<array<string,4>> Ship::getSlotStatusSummaries() const {
	vector<array<string,4>> summary;

	array<string,4> temp;
	for ( int i = MIN_SIDE; i < ALL_SIDES; i++ ) {
		int j = 0;
		for ( auto & it : sides.at(i) ) {
			temp[0] = STRING_SIDE(i);
			temp[1] = STRING_DIRS(ownShipTemplate.getSlotTemplate(i,j).getDirections()); 
			temp[2] = std::to_string(it.actualHp);
			temp[3] = std::to_string(ownShipTemplate.getSlotTemplate(i,j).getMaxHp());

			summary.push_back(temp);
			j++;
		}
	}
	return summary;
}

const ShipTemplate * Ship::getShipTemplate() const{
	const ShipTemplate * p = &ownShipTemplate;
	return p;
}

int Ship::getActualHp() const {return hull.actualHp;}
int Ship::getX() const {return x;}
int Ship::getY() const {return y;}
int Ship::getH() const {return h;}
int Ship::getDirection() const {return direction;}
int Ship::getThrust() const {return hull.thrust;}
int Ship::getLoadoutCost() const {return (getCost() - ownShipTemplate.getCost());}
vector<int> Ship::getTowerLoadouts() const {return hull.controlTowers;}
vector<bool> Ship::getTowerStatuses() const {return hull.dmg.noControlTower;}
